/**
 * 200109：新建
 * 210225：添加alphaTest属性
 * 210824：升级修改【three.js r120-r131】
 */
let THREE=require('three');
import Props from '../general/props';

const DIR=[[-1,-1],[1,-1],[1,1],[-1,1]];
const ZERO_ARRAY=[new THREE.Vector3(0,0,0)];

/**
 * 点群（网格绘制）
 * 渲染行为与hardline类似，渲染三角面，而非points，因而可以避免points的窗体resize不自动更新，正交投影缩放不正常等类似问题
 * 效率会低于points
 *
 * 通过material来编辑材质通用属性
 * 通过geometry来编辑几何体通用属性
 */
export default class MeshPoints extends THREE.Object3D{
    constructor(vertices,values={}){
        super(values);
        console.warn('this class is deprecated now!');
        this.isMesh=true;
        this.drawMode=THREE.TrianglesDrawMode;
        this.geometry=new THREE.BufferGeometry();
        this.material=new THREE.ShaderMaterial();
        this._verts=!Array.isArray(vertices)?ZERO_ARRAY:(vertices.length===0?ZERO_ARRAY:vertices);
        this._values=values;
        this.init();
    }

    //---------------------------------------对外属性及方法---------------------------------------

    /**
     * 属性对象
     * @returns {Props}
     */
    get props(){
        return this._props;
    }
    set props(values){
        for(let key in values){
            if(this._props[''+key+'']){
                this._props[''+key+''].value=values[''+key+''];
            }else{
                console.warn(''+key+' is not a prop of MeshPoints!');
            }
        }
    }

    /**
     * 顶点
     * @returns {array}
     */
    get vertices(){
        return this._verts;
    }
    set vertices(vertices){
        if(vertices.length===0){
            this._verts=ZERO_ARRAY;
        }else{
            this._verts=vertices;
        }
        this.updateGeometry();
    }

    /**
     * 获取顶点着色器
     * @returns {string}
     */
    get vertexShader(){
        return [
            this._props.map.value?'#define USE_UV_TRANSFORM':'',
            '#ifdef USE_UV_TRANSFORM' ,
            '   uniform mat3 uvTransform;' ,
            '#endif',
            'attribute vec2 direction;' ,
            'uniform float size;' ,
            'uniform float perspectiveFactor;' ,
            'uniform bool sizeAttenuation;' ,
            'uniform float zOffset;' ,
            'varying vec2 vuv;' ,
            'float computeAspect(){' ,
            '   float aspect=0.5625;' ,
            '   if(projectionMatrix[3][3]==0.0){' ,
            '      float _tan=projectionMatrix[1][1];' ,
            '      float as_tan=1.0/projectionMatrix[0][0];' ,
            '      aspect=1.0/(as_tan*_tan);' ,
            '   }else{' ,
            '      float _height=1.0/projectionMatrix[1][1];' ,
            '      float _width=1.0/projectionMatrix[0][0];' ,
            '      aspect=_height/_width;' ,
            '   }' ,
            '   return aspect;' ,
            '}',
            'void main(){' ,
            '   vec4 mvPosition=modelViewMatrix*vec4(position,1.);' ,
            '   vec4 pmvPosition=projectionMatrix*mvPosition;' ,
            '   vec3 dir=normalize(vec3(direction,0.))*size;' ,
            '   float aspect=computeAspect();' ,
            '   dir.x*=aspect;' ,
            '   pmvPosition.z+=zOffset;',
            '   if(sizeAttenuation){' ,
            '       dir*=projectionMatrix[1][1];' ,
            '       gl_Position=vec4(vec3(pmvPosition.xyz)+dir*perspectiveFactor,pmvPosition.w);' ,
            '   }else{' ,
            '       gl_Position=vec4(vec3(pmvPosition.xyz)+dir*perspectiveFactor*pmvPosition.w,pmvPosition.w);' ,
            '   }' ,
            '   #ifdef USE_UV_TRANSFORM' ,
            '       vuv=(uvTransform*vec3(direction*0.5+0.5,1.)).xy;' ,
            '   #else' ,
            '       vuv=direction*0.5+0.5;' ,
            '   #endif' ,
            '}'
        ].join('\n');
    }

    /**
     * 获取片元着色器
     * @returns {string}
     */
    get fragmentShader(){
        return [
            this._props.map.value?'#define MAP_AVAILABLE':'' ,
            '#ifdef MAP_AVAILABLE' ,
            '   uniform sampler2D map;' ,
            '#endif' ,
            'uniform vec3 color;' ,
            'uniform float opacity;' ,
            'uniform float alphaTest;',
            'varying vec2 vuv;' ,
            'void main(){' ,
            '   #ifdef MAP_AVAILABLE' ,
            '       vec4 col=texture2D(map,vuv);' ,
            '       if(col.a<alphaTest)discard;' ,
            '       col.r*=color.r;' ,
            '       col.g*=color.g;' ,
            '       col.b*=color.b;' ,
            '       gl_FragColor=vec4(col.rgb,opacity*col.a);' ,
            '   #else' ,
            '       gl_FragColor=vec4(color,opacity);' ,
            '   #endif' ,
            '}'
        ].join('\n');
    }

    /**
     * 单点大小
     * @returns {*}
     */
    get size(){
        return this._props.size.value;
    }
    set size(num){
        this._props.size.value=num;
    }

    /**
     * 颜色（与贴图相乘）
     * @returns {*}
     */
    get color(){
        return this._props.color.value;
    }
    set color(color){
        this._props.color.value=color;
    }

    /**
     * 贴图
     * @returns {*}
     */
    get map(){
        return this._props.map.value;
    }
    set map(tex){
        this._props.map.value=tex;
    }

    /**
     * 不透明度（与贴图相乘）
     * @returns {*}
     */
    get opacity(){
        return this._props.opacity.value;
    }
    set opacity(num){
        this._props.opacity.value=num;
    }

    /**
     * 透视系数（不常用，透视与正交有切换时有用）
     * @returns {*}
     */
    get perspectiveFactor(){
        return this._props.perspectiveFactor.value;
    }
    set perspectiveFactor(num){
        this._props.perspectiveFactor.value=num;
    }

    /**
     * 启用近大远小
     * @returns {*}
     */
    get sizeAttenuation(){
        return this._props.sizeAttenuation.value;
    }
    set sizeAttenuation(bol){
        this._props.sizeAttenuation.value=bol;
    }

    /**
     * 深度偏移
     * @returns {*}
     */
    get zOffset(){
        return this._props.zOffset.value;
    }
    set zOffset(num){
        this._props.zOffset.value=num;
    }

    /**
     * 射线检测倍率（与size共用作用）
     * @returns {*}
     */
    get castRatio(){
        return this._props.castRatio.value;
    }
    set castRatio(num){
        this._props.castRatio.value=num;
    }

    /**
     * alpha 过滤
     */
    get alphaTest(){
        return this._props.alphaTest.value;
    }
    set alphaTest(num){
        this._props.alphaTest.value=num;
    }

    /**
     * 更新几何体（不含包围球）
     */
    updateGeometry(){
        let l=this._verts.length;
        if(l*6>this._indices.length){
            this.geometry.dispose();
            let c=l*2;
            this._positions=new Float32Array(c*12);
            this._directions=new Int8Array(c*8);
            this._indices=new Uint16Array(c*6);
            this.setAttribute();
        }
        this.setInfo();
        this.geometry.index.copyArray(this._indices);
        this.geometry.getAttribute('position').copyArray(this._positions);
        this.geometry.getAttribute('direction').copyArray(this._directions);
        this.geometry.index.needsUpdate=true;
        this.geometry.getAttribute('position').needsUpdate=true;
        this.geometry.getAttribute('direction').needsUpdate=true;
        this.geometry.setDrawRange(0,this._verts.length*6);
    }

    /**
     * 计算包围球
     */
    computeBoundingSphere(){
        if(!this.geometry.boundingSphere)this.geometry.boundingSphere=new THREE.Sphere();
        if(this._verts.length===1){
            this.geometry.boundingSphere.center.copy(this._verts[0]);
            this.geometry.boundingSphere.radius+=this._props.size.value;
        }else{
            this.geometry.boundingSphere.setFromPoints(this._verts);
            this.geometry.boundingSphere.radius+=this._props.size.value;
        }
    }

    /**
     * 拷贝
     * @param source {MeshPoints}
     * @param recursive {boolean}
     */
    copy(source,recursive=false){
        super.copy(source,recursive);
        this.material.copy(source.material);
        this.material.needsUpdate=true;
        this.props=source.props.all(true);
        this.vertices=source.vertices;
        this.computeBoundingSphere();
        return this;
    }

    /**
     * 销毁
     */
    dispose(){
        this.geometry.dispose();
        if(this.material.uniforms.map.value){
            this.material.uniforms.map.value.dispose();
        }
        this.material.dispose();
        this._positions=null;
        this._directions=null;
        this._indices=null;
        this._sphere=null;
        this._invMat=null;
        this._ray=null;
        this._tV0=null;
        this._props.dispose();
    }

    //--------------------------------------------私有属性和方法--------------------------------------------------

    initialize(){}
    init(){
        this.initProps();
        this.initVariable();
        this.initGeometry();
        this.initMaterial();
        this.initRaycast();
        this.initPropCallback();
        this.initialize();
    }
    initProps(){
        this._props=new Props({
            size:this._values.size||1,
            color:this._values.color||new THREE.Color(1,1,1),
            map:this._values.map||null,
            opacity:this._values.opacity!==undefined?this._values.opacity:1,
            zOffset:this._values.zOffset!==undefined?this._values.zOffset:0,
            perspectiveFactor: this._values.perspectiveFactor||0.5,
            sizeAttenuation: this._values.sizeAttenuation!==undefined?this._values.sizeAttenuation:true,
            castRatio:this._values.castRatio||0.1,
            alphaTest:this._values.alphaTest||0,
        });
    }
    initPropCallback(){
        this._props.onKeyChange('size',(_new,_old)=>{
            this.material.uniforms.size.value=_new;
        });
        this._props.onKeyChange('alphaTest',(_new,_old)=>{
            this.material.uniforms.alphaTest.value=_new;
        });
        this._props.onKeyChange('color',(_new,_old)=>{
            this.material.uniforms.color.value=_new;
        });
        this._props.onKeyChange('map',(_new,_old)=>{
            if((!_old&&_new)||(_old&&!_new)){
                this.material.vertexShader=this.vertexShader;
                this.material.fragmentShader=this.fragmentShader;
            }
            this.material.uniforms.map.value=_new;
            this.material.uniforms.uvTransform.value=_new.matrix;
            this.material.needsUpdate=true;
        });
        this._props.onKeyChange('opacity',(_new,_old)=>{
            this.material.uniforms.opacity.value=_new;
        });
        this._props.onKeyChange('perspectiveFactor',(_new,_old)=>{
            this.material.uniforms.perspectiveFactor.value=_new;
        });
        this._props.onKeyChange('sizeAttenuation',(_new,_old)=>{
            this.material.uniforms.sizeAttenuation.value=_new;
        });
        this._props.onKeyChange('zOffset',(_new,_old)=>{
            this.material.uniforms.zOffset.value=_new;
        });
    }
    initVariable(){
        let c=this._verts.length*2;
        this._positions=new Float32Array(c*4*3);
        this._directions=new Int8Array(c*4*2);
        this._indices=new Uint16Array(c*2*3);
        this._sphere=new THREE.Sphere();
        this._invMat=new THREE.Matrix4();
        this._ray=new THREE.Ray();
        this._tV0=new THREE.Vector3();
    }
    setInfo(){
        for(let i=0;i<this._verts.length;i++) {
            let v = this._verts[i];
            let block = i * 4;
            for (let j = 0; j < 4; j++) {
                this._positions[(block + j) * 3] = v.x;
                this._positions[(block + j) * 3 + 1] = v.y;
                this._positions[(block + j) * 3 + 2] = v.z;
                this._directions[(block + j) * 2] = DIR[j][0];
                this._directions[(block + j) * 2 + 1] = DIR[j][1];
            }
            this._indices[i * 6] = block;
            this._indices[i * 6 + 1] = block + 1;
            this._indices[i * 6 + 2] = block + 2;
            this._indices[i * 6 + 3] = block;
            this._indices[i * 6 + 4] = block + 2;
            this._indices[i * 6 + 5] = block + 3;
        }
    }
    initGeometry(){
        this.setInfo();
        this.setAttribute();
        this.computeBoundingSphere();
    }
    setAttribute(){
        this.geometry.setIndex(new THREE.Uint16BufferAttribute(this._indices,1));
        this.geometry.setAttribute('position',new THREE.Float32BufferAttribute(this._positions,3));
        this.geometry.setAttribute('direction',new THREE.Int8BufferAttribute(this._directions,2));
        this.geometry.setDrawRange(0,this._verts.length*6);
    }
    initMaterial(){
        this.initShaders();
        this.initUniforms();
    }
    initShaders(){
        this.material.vertexShader=this.vertexShader;
        this.material.fragmentShader=this.fragmentShader;
    }
    initUniforms(){
        this.material.uniforms={
            size:{value:this._props.size.value},
            sizeAttenuation:{value:this._props.sizeAttenuation.value},
            map:{value:this._props.map.value},
            color:{value:this._props.color.value},
            opacity:{value:this._props.opacity.value},
            perspectiveFactor:{value:this._props.perspectiveFactor.value},
            zOffset:{value:this._props.zOffset.value},
            uvTransform:{value:this._props.map.value?this._props.map.value.matrix:null},
            alphaTest:{value:this._props.alphaTest.value?this._props.alphaTest.value:0},
        };
        this.material.depthWrite=false;
    }
    initRaycast() {
        this.raycast = (raycaster, intersects) => {
            let object = this;
            let geometry = this.geometry;
            let matrixWorld = this.matrixWorld;
            let threshold = this._props.size.value * this._props.castRatio.value;
            if (geometry.boundingSphere === null) this.computeBoundingSphere();
            this._sphere.copy(geometry.boundingSphere);
            this._sphere.applyMatrix4(matrixWorld);
            this._sphere.radius += threshold;
            if (raycaster.ray.intersectsSphere(this._sphere) === false) return;
            // this._invMat.getInverse(matrixWorld);20210824
            this._invMat.copy(matrixWorld).invert();
            this._ray.copy(raycaster.ray).applyMatrix4(this._invMat);
            let localThreshold = threshold / ((this.scale.x + this.scale.y + this.scale.z) / 3);
            let localThresholdSq = localThreshold * localThreshold;
            let testPoint = (point, index) => {
                let rayPointDistanceSq = this._ray.distanceSqToPoint(point);
                if (rayPointDistanceSq < localThresholdSq) {
                    this._ray.closestPointToPoint(point, this._tV0);
                    this._tV0.applyMatrix4(matrixWorld);
                    let distance = raycaster.ray.origin.distanceTo(this._tV0);
                    if (distance < raycaster.near || distance > raycaster.far) return;
                    intersects.push({
                        distance: distance,
                        distanceToRay: Math.sqrt(rayPointDistanceSq),
                        point: this._tV0.clone(),
                        index: index,
                        face: null,
                        object: object
                    });
                }
            };
                for (let i = 0; i < this._verts.length; i++) {
                    testPoint(this._verts[i], i);
                }
        };
    }
}